/*
 * Sylvain BERTRAND <digital.ragnarok@gmail.com>
 * code protected by GNU GPL v3
 */
#include <X11/Xlib.h>
#include <X11/extensions/sync.h>
#include <glib.h>
#include <libxml/xinclude.h>
#include "../nls.h"
#include "../toolkit/display.h"
#include "../toolkit/xml.h"
#include "../toolkit/keyboard.h"
#include "misc.h"
#include "wm.h"
#include "grab.h"
#include "keytree.h"
#include "geom.h"
#include "window.h"
#include "misc.h"
#include "stacking.h"
#include "client.h"
#include "config.h"
#include "place.h"
#include "moveresize.h"
#include "frame.h"
#include "actions.h"
#include "keyboard.h"
#include "screen.h"
struct wm_key_binding_tree *keyboard_firstnode = NULL;
static struct wm_key_binding_tree *curpos;
static guint chain_timer = 0;
static void grab_keys(gboolean grab)
{
	struct wm_key_binding_tree *p;
	ungrab_all_keys(DefaultRootWindow(t_display));
	if(grab) {
		p = curpos ? curpos->first_child : keyboard_firstnode;
		while(p) {
			if(p->key)
				grab_key(p->key, p->state, DefaultRootWindow(t_display), GrabModeAsync);
			p = p->next_sibling;
		}
		if(curpos)
			grab_key(config_keyboard_reset_keycode, config_keyboard_reset_state, DefaultRootWindow(t_display), GrabModeAsync);
	}
}

static gboolean chain_timeout(gpointer data)
{
	(void)data;
	keyboard_reset_chains(0);
	return FALSE;
}

static void chain_done(gpointer data)
{
	(void)data;
	chain_timer = 0;
}

static void set_curpos(struct wm_key_binding_tree *newpos)
{
	if(curpos == newpos)
		return;
	grab_keys(FALSE);
	curpos = newpos;
	grab_keys(TRUE);
	if(curpos != NULL) {
		gchar *text = NULL;
		GList *it;
		const struct wm_rect *a;
		for(it = curpos->keylist; it; it = g_list_next(it)) {
			gchar *oldtext = text;
			if(text == NULL)
				text = g_strdup(it->data);
			else
				text = g_strconcat(text, " - ", it->data, NULL);
			g_free(oldtext);
		}
		a = screen_physical_area_primary(FALSE);
		g_free(text);
	}
}

void keyboard_reset_chains(gint break_chroots)
{
	struct wm_key_binding_tree *p;
	for(p = curpos; p; p = p->parent) {
		if(p->chroot) {
			if(break_chroots == 0)
				break;
			if(break_chroots > 0)
				--break_chroots;
		}
	}
	set_curpos(p);
}

void keyboard_unbind_all(void)
{
	tree_destroy(keyboard_firstnode);
	keyboard_firstnode = NULL;
}

void keyboard_chroot(GList *keylist)
{
	if(!tree_chroot(keyboard_firstnode, keylist)) {
		struct wm_key_binding_tree *tree;
		if(!(tree = tree_build(keylist)))
			return;
		tree_chroot(tree, keylist);
		tree_assimilate(tree);
	}
}

gboolean keyboard_bind(GList *keylist, struct wm_actions_act *action)
{
	struct wm_key_binding_tree *tree, *t;
	gboolean conflict;
	g_assert(keylist != NULL);
	g_assert(action != NULL);
	if(!(tree = tree_build(keylist)))
		return FALSE;
	if((t = tree_find(tree, &conflict)) != NULL) {
		tree_destroy(tree);
		tree = NULL;
	} else
		t = tree;
	if(conflict) {
		g_message(_("Conflict with key binding in config file"));
		tree_destroy(tree);
		return FALSE;
	}
	for(; t->first_child; t = t->first_child);
	t->actions = g_slist_append(t->actions, action);
	if(tree)
		tree_assimilate(tree);
	return TRUE;
}

gboolean keyboard_event(struct wm_client *client, const XEvent *e)
{
	struct wm_key_binding_tree *p;
	gboolean used;
	guint mods;
	if(e->type == KeyRelease) {
		grab_key_passive_count(-1);
		return FALSE;
	}
	g_assert(e->type == KeyPress);
	grab_key_passive_count(1);
	mods = t_keyboard_only_modmasks(e->xkey.state);
	if(e->xkey.keycode == config_keyboard_reset_keycode && mods == config_keyboard_reset_state) {
		if(chain_timer)
			g_source_remove(chain_timer);
		keyboard_reset_chains(-1);
		return TRUE;
	}
	used = FALSE;
	if(curpos == NULL)
		p = keyboard_firstnode;
	else
		p = curpos->first_child;
	while(p) {
		if(p->key == e->xkey.keycode && p->state == mods) {
			if(p->first_child != NULL) {
				if(chain_timer)
					g_source_remove(chain_timer);
				chain_timer = g_timeout_add_full(G_PRIORITY_DEFAULT, 3000, chain_timeout, NULL, chain_done);
				set_curpos(p);
			} else if(p->chroot)
				set_curpos(p);
			else {
				GSList *it;
				for(it = p->actions; it; it = g_slist_next(it))
					if(actions_act_is_interactive(it->data))
						break;
				if(it == NULL)
					keyboard_reset_chains(0);
				actions_run_acts(p->actions, WM_USER_ACTION_KEYBOARD_KEY, e->xkey.state, e->xkey.x_root, e->xkey.y_root, 0,
					WM_FRAME_CONTEXT_NONE, client);
			}
			break;
			used = TRUE;
		}
		p = p->next_sibling;
	}
	return used;
}

static void node_rebind(struct wm_key_binding_tree *node)
{
	if(node->first_child) {
		node_rebind(node->first_child);
		if(node->chroot)
			keyboard_chroot(node->keylist);
	} else {
		while(node->actions) {
			keyboard_bind(node->keylist, node->actions->data);
			node->actions = g_slist_delete_link(node->actions, node->actions);
		}
		if(node->chroot)
			keyboard_chroot(node->keylist);
	}
	if(node->next_sibling)
		node_rebind(node->next_sibling);
}

void keyboard_rebind(void)
{
	struct wm_key_binding_tree *old;
	old = keyboard_firstnode;
	keyboard_firstnode = NULL;
	node_rebind(old);
	tree_destroy(old);
	set_curpos(NULL);
	grab_keys(TRUE);
}

void keyboard_startup(void)
{
	grab_keys(TRUE);
}

void keyboard_shutdown(void)
{
	if(chain_timer)
		g_source_remove(chain_timer);
	keyboard_unbind_all();
	set_curpos(NULL);
}
